package v9.assist.access

import android.accessibilityservice.GestureDescription
import android.accessibilityservice.GestureDescription.StrokeDescription
import android.graphics.Path
import android.os.Build
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.view.animation.OvershootInterpolator
import sample.ui.Properties
import abc.reflect.App
import android.content.ClipboardManagerService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.script.Block
import kotlinx.coroutines.script.C
import kotlinx.coroutines.script.Controller
import kotlinx.coroutines.script.GComponents
import kotlinx.coroutines.script.tes.Accessibility
import kotlinx.coroutines.script.tes.DiskLogUtils
import kotlinx.coroutines.script.tes.Keys
import kotlinx.coroutines.script.tes.phoneScreenHeight
import kotlinx.coroutines.script.tes.phoneScreenWidth
import kotlinx.coroutines.script.tes.printLog
import kotlinx.coroutines.script.tes.printLogE
import kotlinx.coroutines.script.tes.pt
import kotlinx.coroutines.script.tes.randomFloat
import kotlinx.coroutines.script.tes.shake
import kotlinx.coroutines.script.usableSkillAlways
import kotlinx.coroutines.withContext
import v9.assist.analyst.PAGE_MAIN
import v9.assist.analyst.anaCounterNoSkill
import v9.assist.analyst.anaLastPageNotMainTs
import v9.assist.analyst.bsPage
import v9.assist.analyst.bsPlayerHp
import v9.assist.analyst.bsSceneSettle
import v9.assist.analyst.bsSceneSettleBlacksmith
import v9.assist.analyst.selfInSmallMap
import v9.assist.script.releaseGActionByInteract
import v9.assist.script.xLastAtvTs
import v9.assist.script.xLastInteractTs
import v9.assist.script.xLastUseMedicineTs
import kotlin.math.hypot
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt

private const val atk = "atk"
private const val pathfinding = "pathfinding"
private const val click = "click"
private const val tracelessClick = "tracelessClick"
private val medicine by lazy { GComponents.get().abPanel.medicine }
private val rocker by lazy { GComponents.get().rocker }
private val sTime by lazy {
    3L
}
private val splitTime: Long get() = 250L.shake(2)
private val tapTime: Long get() { // 这里就留2中操作模式把
    return 111L
}
private val rockerDownRandomPoint: XPoint get() {
    return XPoint(rocker.x.toFloat(), rocker.y.toFloat())
}
private fun takeRockerLength(angle: Int): Int {
    return 160.pt
}
private fun takeBtnTapTime(btnName: String?): Long {
    return when (btnName) {
        btnSkillA -> max(tapTime, GComponents.get().abPanel.skillA.longPressTs.shake(10))
        btnSkillB -> max(tapTime, GComponents.get().abPanel.skillB.longPressTs.shake(10))
        btnSkillC -> max(tapTime, GComponents.get().abPanel.skillC.longPressTs.shake(10))
        btnSkillD -> max(tapTime, GComponents.get().abPanel.skillD.longPressTs.shake(10))
        btnAtk -> max(splitTime, GComponents.get().abPanel.atk.longPressTs.shake(10))
        else -> tapTime
    }
}
//private const val angle0 = 0
//private const val angle30 = 30
//private const val angle60 = 60
//private const val angle90 = 90
//private const val angle120 = 120
//private const val angle150 = 150
//private const val angle180 = 180
//private const val angle210 = 210
//private const val angle240 = 240
//private const val angle270 = 270
//private const val angle300 = 300
//private const val angle330 = 330
//private fun takeBlockAngle(angle: Int): Int {
//    return when (angle) {
//        in 15 .. 45 -> angle30
//        in 46 .. 75 -> angle60
//        in 76 .. 105 -> angle90
//        in 106 .. 135 -> angle120
//        in 136 .. 165 -> angle150
//        in 166 .. 195 -> angle180
//        in 196 .. 225 -> angle210
//        in 226 .. 255 -> angle240
//        in 256 .. 285 -> angle270
//        in 286 .. 315 -> angle300
//        in 316 .. 345 -> angle330
//        else -> angle0
//    }
//}

//private const val angle0 = 0
//private const val angle45 = 45
//private const val angle90 = 90
//private const val angle135 = 135
//private const val angle180 = 180
//private const val angle225 = 225
//private const val angle270 = 270
//private const val angle315 = 315
//private fun takeBlockAngle(angle: Int): Int {
//    return when (angle) {
//        in 22 .. 67 -> angle45
//        in 68 .. 112 -> angle90
//        in 113 .. 157 -> angle135
//        in 158 .. 202 -> angle180
//        in 203 .. 247 -> angle225
//        in 248 .. 292 -> angle270
//        in 293 .. 337 -> angle315
//        else -> angle0
//    }
//}

private var counterInterceptor = 200.shake(100)

suspend fun accClickTraceless(name: String, afterDelay: Long = 0) {
    printLog { "accClick traceless click $name" }
    innerPlayGestures(name = tracelessClick, rockerAngle = null, btn = name, traceless = true)

    if (afterDelay > 2000) delay(afterDelay.shake(300))
    else if (afterDelay > 1000) delay(afterDelay.shake(200))
    else if (afterDelay > 40) delay(afterDelay.shake(30))
    else if (afterDelay > 0) delay(afterDelay)
}
// 上层一点
suspend fun accClick(name: String, afterDelay: Long = 0) {
    printLog { "accClick $name" }
    if (name == btnMedicine) xLastUseMedicineTs = System.currentTimeMillis()
    if (name == btnInteract) xLastInteractTs = System.currentTimeMillis()
    if (name == btnInteract) releaseGActionByInteract++
    if (releaseGActionByInteract > 0 && releaseGActionByInteract % 10 == 0) {
        releaseLastGestures() // 弹出了他人的信息菜单.需要重新摇杆消除弹窗
        releaseGActionByInteract++
    }
    if (releaseGActionByInteract > 10000) releaseGActionByInteract = 1

    innerPlayGestures(name = click, rockerAngle = null, btn = name)

    if (afterDelay > 2000) delay(afterDelay.shake(300))
    else if (afterDelay > 1000) delay(afterDelay.shake(200))
    else if (afterDelay > 40) delay(afterDelay.shake(30))
    else if (afterDelay > 0) delay(afterDelay)
}

suspend fun accPathfindingMapTarget(target: Block?, itemAction: suspend (index: Int, angle: Int)->Unit) {
    if (target == null) return
    if (bsPage != PAGE_MAIN) return
    if (System.currentTimeMillis() - anaLastPageNotMainTs < 500) return // 避免卡摇杆
    var offsetX = target.x - selfInSmallMap.x
    var offsetY = target.y - selfInSmallMap.y
    while (offsetX == 0 && offsetY == 0) {
        offsetX = 0.shake(6)
        offsetY = 0.shake(6)
    }
    val distance = sqrt((offsetX * offsetX + offsetY * offsetY).toDouble()).toInt()
    val moveTime = min(3600L, max(splitTime, (distance / 1f.pt * 27).toLong())) // 1f.pt * 21
    val moveStep: Int = max(1, (moveTime / splitTime).toInt())
    val angle = App.take().pointToAngle(offsetX, offsetY)
    printLog { "accPathfindingMapTarget [${target.name}] angle $angle moveStep $moveStep movingTime $moveTime distance $distance" }
    repeat(moveStep) {
        if (bsPage == PAGE_MAIN) {
            itemAction.invoke(it, angle)
        }
    }
    releaseLastGestures()
}
suspend fun accPathfinding(angle: Int?, btn: String? = null, disableSkillAlways: Boolean = false) {
    if (angle == null) return
//    if (bsPage != PAGE_MAIN) return
    if (System.currentTimeMillis() - anaLastPageNotMainTs < 500) return // 避免卡摇杆

    if (anaCounterNoSkill > 330) {
        releaseLastGestures() // 消除键盘.画面能看到人物血条.加半个屏幕的输入法.摇杆在字母上乱按
        accClick(btnCenterTop, afterDelay = 400) // 消除键盘
        accClick(btnCenterTop) // 消除键盘
        anaCounterNoSkill = 0
    }

    val btn2: String? = if (btn.isNullOrEmpty() && medicine.usable && bsPlayerHp < 0.8f && System.currentTimeMillis() - xLastUseMedicineTs > 6000) {
        btnMedicine
    } else if (!disableSkillAlways && btn.isNullOrEmpty() && !bsSceneSettle && !bsSceneSettleBlacksmith) {
        usableSkillAlways?.also { // 新增常驻寻路技能释放
//          这里打断的技能释放  releaseLastGestures()
            it.useV2(angle)
        }
        btn
    } else btn

    if (btn2 == btnMedicine) xLastUseMedicineTs = System.currentTimeMillis()
    if (btn2 == btnInteract) xLastInteractTs = System.currentTimeMillis()
    if (btn2 == btnInteract) releaseGActionByInteract++
    if (releaseGActionByInteract > 0 && releaseGActionByInteract % 10 == 0) {
        releaseLastGestures() // 弹出了他人的信息菜单.需要重新摇杆消除弹窗
        releaseGActionByInteract++
    }
    if (releaseGActionByInteract > 10000) releaseGActionByInteract = 1

    innerPlayGestures(name = pathfinding, rockerAngle = angle, rockerTime = splitTime.shake(10), btn = btn2)
}

suspend fun accAtkByKit(angleValue: Int?, btn: String) {
    printLog { "accAtk btn $btn angle $angleValue" }

    if (btn == btnMedicine) xLastUseMedicineTs = System.currentTimeMillis()
    if (btn == btnInteract) xLastInteractTs = System.currentTimeMillis()
    if (btn == btnInteract) releaseGActionByInteract++
    if (releaseGActionByInteract > 0 && releaseGActionByInteract % 10 == 0) {
        releaseLastGestures() // 弹出了他人的信息菜单.需要重新摇杆消除弹窗
        releaseGActionByInteract++
    }
    if (releaseGActionByInteract > 10000) releaseGActionByInteract = 1

    val btnTime = takeBtnTapTime(btn)
    val rockerTime = splitTime.shake(10) // if (System.currentTimeMillis() - anaLastHasEliteEnemyNameTs < 3000) 270L.shake(10) else max(btnTime, XGesturePoint.tapTime)
    innerPlayGestures(name = atk, rockerAngle = angleValue, rockerTime = rockerTime, btn = btn, btnTime = btnTime)
}

data class XPoint(val x: Float, val y: Float)

private val path1ByStrategy: Boolean get() {
    return continueCounter++ > counterInterceptor
}
var continueCounter = 0
private var lastRockerDown: XPoint? = null
private var lastRockerMove: XPoint? = null
private var lastBaseAtk: XPoint? = null


data class GAction(
    val name: String,
    val rockerAngle: Int?,
    val rockerTime: Long = tapTime,
    val btn: String?,
    val btnTime: Long = tapTime,
    val end: Boolean = false,
    val lastSd1: StrokeDescription?,
    val lastSd2: StrokeDescription?,
    val replacedCurrSd1: StrokeDescription? = null 
) {
    private var path2EndX: Float? = null
    private var path2EndY: Float? = null
    private var path1Angle: Int? = null
    private var path1EndX: Float? = null
    private var path1EndY: Float? = null

    fun takePath1End(): Path? {
        val p1x = path1EndX ?: return null
        val p1y = path1EndY ?: return null
        val out = Path()
        out.moveTo(p1x, p1y)
        return out
    }
    fun takePath2End(): Path? {
        val p2x = path2EndX ?: return null
        val p2y = path2EndY ?: return null
        val out = Path()
        out.moveTo(p2x, p2y)
        return out
    }

    private val path1: List<Path>? by lazy { // rocker
        val prefabPath1Angle = path1Angle // 普攻中断后继续
        val prefabPath1EndX = path1EndX
        val prefabPath1EndY = path1EndY
        if (prefabPath1Angle != null && prefabPath1EndX != null && prefabPath1EndY != null) {
            val out = Path() // 这个逻辑返回不重要.主要是不会去干扰 'lastRockerMove' 导致卡死
            out.moveTo(prefabPath1EndX, prefabPath1EndY)
            listOf(out)
        } else if (path1ByStrategy) {
            counterInterceptor = 300.shake(150)
            continueCounter = 0
            path1Angle = null
            path1EndX = null
            path1EndY = null
            lastRockerDown = null
            lastRockerMove = null
            null
        } else {
            val atvTs = System.currentTimeMillis() - xLastAtvTs
            val sa = when  {
                Properties.scene == C.S_DYWS -> 60 // 打野幅度大点
                Properties.scene == C.S_FB -> 10 // 避免船内乱走
                atvTs in 0..1000 -> 10  // 怪物打架的时段精准移动
                atvTs in 1000..3000 -> 10
                atvTs in 3000..8000 -> 10
                atvTs in 8000..16000 -> 10
                atvTs in 16000..24000 -> 15
                atvTs in 24000..30000 -> 40
                else -> 50
            }
            var angle = rockerAngle?.shake(sa) ?: return@lazy null
//            angle = takeBlockAngle(angle) // 角度归一化
            path1Angle = angle
            val rockerDown = lastRockerDown ?: rockerDownRandomPoint
            val rockerMove = lastRockerMove ?: rockerDown
            if (lastRockerDown == null) lastRockerDown = rockerDown
            val startX = rockerMove.x
            val startY = rockerMove.y
            // 这里差不多有230的空间.因为向上调整了60.开始只有170的空间
            val endXY = App.take().angleToPoint(rockerDown.x, rockerDown.y, angle, takeRockerLength(angle))
            val endX = endXY.first
            val endY = endXY.second
            path1EndX = endX
            path1EndY = endY
            lastRockerMove = XPoint(endX, endY)
            createHumanLikePathList(startX, startY, endX, endY)
        }
    }
    private val path2: Path? by lazy {
        val btnBlock = takeBtn(btn) ?: return@lazy null
        val baseAtk = lastBaseAtk
        if (btn == btnAtk && baseAtk != null) {
            // 继续返回上次的
            path2EndX = baseAtk.x
            path2EndY = baseAtk.y
            val out = Path()
            out.moveTo(baseAtk.x, baseAtk.y)
            out
        } else {
            val endXY = btnBlock.x.shake((btnBlock.width * 0.42f).toInt()) to btnBlock.y.shake((btnBlock.height * 0.42f).toInt())
            val endX: Float = max(clickAreaEdge, min(phoneScreenWidth - clickAreaEdge, endXY.first.randomFloat))
            val endY: Float = max(clickAreaEdge, min(phoneScreenHeight - clickAreaEdge, endXY.second.randomFloat))
            if (btn == btnAtk) {
                lastBaseAtk = XPoint(endX, endY)
                path2EndX = endX
                path2EndY = endY
            }
            val out = Path()
            out.moveTo(endX, endY)
            out
        }
    }

    private val sd1: List<StrokeDescription> by lazy {
        if (replacedCurrSd1 != null) {
            // 替换了上次的摇杆. 直接返回
            return@lazy listOf(replacedCurrSd1)
        }

        val out = mutableListOf<StrokeDescription>()
        val linkLast = lastSd1?.willContinue() == true
        var tempLast: StrokeDescription? = null
        path1?.forEachIndexed { index, p1 ->
            val ls1 = lastSd1
            val s = if (ls1 != null && linkLast && index == 0) {
                // 最后一个path. 继续上次的
                ls1.continueStroke(p1, 0, sTime, true)
            } else {
                tempLast?.continueStroke(p1, 0, sTime, true)
                    ?: StrokeDescription(p1, 0, sTime, true)
            }
            out.add(s)
            tempLast = s
        }
        out
    }
//
    private val sd2: List<StrokeDescription> by lazy {
        val out = mutableListOf<StrokeDescription>()
        val linkLast = lastSd2?.willContinue() == true

        val nextBaseWillContinue = false
        var tempLast: StrokeDescription? = null
        path2?.also { p2 ->
            val p1Size = path1?.size ?: 0
//            val nextBaseWillContinue = btn == btnAtk && p1Size > 0  // && atkContinueByStrategy

            val c = if (p1Size == 0) (btnTime / sTime).toInt()
            else max(1, if (nextBaseWillContinue) p1Size else min(p1Size - 10, (btnTime / sTime).toInt()))

//            val originC = (btnTime / sTime).toInt()
//            val min = min(p1Size - 1, originC)
//            val max = max(p1Size - 1, originC)
//            val fixedC = if (btn == btnAtk) (min .. max).random() else originC
//            val c = max(1, min(p1Size - 1, fixedC))
//            val c = max(1, if (nextBaseWillContinue) p1Size else min(p1Size - 1, (btnTime / sTime).toInt()))
//            val c = max(1, min(p1Size - 1, (btnTime / sTime).toInt()))

            repeat(c) { index ->
                val ls2 = lastSd2
                val nwc = (index < c - 1) || nextBaseWillContinue
                val s = if (ls2 != null && linkLast && index == 0) {
                    // 最后一个path. 继续上次的
                    ls2.continueStroke(p2, 0, sTime, nwc) // 非普攻会自己收掉结尾
                } else {
                    tempLast?.continueStroke(p2, 0, sTime, nwc) // 非普攻会自己收掉结尾
                        ?: StrokeDescription(p2, 1, sTime, nwc) // 非普攻会自己收掉结尾
                }
                out.add(s)
                tempLast = s
            }
        }
        out
    }

    val strokeDescriptions by lazy {
        val out = mutableListOf<Pair<StrokeDescription?, StrokeDescription?>>()
        repeat(max(sd1.size, sd2.size)) { index ->
            out.add(sd1.getOrNull(index) to sd2.getOrNull(index))
        }
        out
    }

    override fun toString(): String {
        val out = StringBuilder()
        if (path1Angle != null) out.append("[摇杆(a:$path1Angle)时长:${rockerTime}]")
        if (btn?.isNotEmpty() == true) {
            out.append("[${btn}(时长:${btnTime};${takeBtn(btn)?.x},${takeBtn(btn)?.y})]")
        }
        return out.toString()
    }

    fun replaceExcludeBtn(invokedSd1: StrokeDescription?): GAction {
        return GAction(name, rockerAngle, rockerTime, null, btnTime, end, null, null,
            replacedCurrSd1 = invokedSd1).also {
            it.path1Angle = path1Angle
            it.path1EndX = path1EndX
            it.path1EndY = path1EndY
        }
    }
}

private var lastAction: GAction? = null

private suspend fun innerPlayGestures(
    name: String,
    rockerAngle: Int?,
    rockerTime: Long = tapTime,
    btn: String?,
    btnTime: Long = tapTime,
    end: Boolean = false,
    traceless: Boolean = false,
) = withContext(Dispatchers.Main) {
    // GAction 构建务必在Main. GAction构建内的逻辑在Main线程. 和释放对应
    val service = takeService() ?: return@withContext
    delay(sTime) // 这里和之前的手势错开
    // Android8.0
    if (lastAction?.rockerAngle != null && rockerAngle == null) {
        releaseLastGestures(END_ALL) // 摇杆结束
    }
    else if (lastAction?.btn == btnAtk && btn != btnAtk) {
        releaseLastGestures(END_PATH_2) // 普攻结束
    }
    check()
    val lastActionSdList = lastAction?.strokeDescriptions
    val action = GAction(name, rockerAngle, rockerTime, btn, btnTime, end,
        lastActionSdList?.lastOrNull()?.first, lastActionSdList?.lastOrNull()?.second)

    val sdList = action.strokeDescriptions
    if (sdList.isEmpty()) {
        printLogE { "access playGestures ${action.name} sdList empty" }
        releaseLastGestures( END_ALL) //比如这里有个超次数打断.
        return@withContext
    }
    sdList.forEach { item ->
        val builder = GestureDescription.Builder()
        item.first?.also { builder.addStroke(it) }
        item.second?.also { builder.addStroke(it) }
        val description = builder.build()
        service.dispatchGesture(description, null, null)
        delay(sTime)
    }
    printLog { "access invoked $action" }
    lastAction = action

    // 记录最后一次点击的时间戳
    if (!traceless) lastAccInvokedTs = System.currentTimeMillis()
}

const val END_ALL = 0
private const val END_PATH_2 = 2 // 只释放攻击按钮. 和 技能A|技能B|技能C|技能D|技能E|技能F|互动|血条
suspend fun releaseLastGestures(
    type: Int = END_ALL
) = withContext(Dispatchers.Main) {
    val service = takeService() ?: return@withContext
    val action = lastAction ?: return@withContext
    printLog { "access releaseLastGestures $action $type" }
    val sTime = 3L
    val builder = GestureDescription.Builder()
    var replacedCurrSd1: StrokeDescription? = null
    var emptyStroke = true
        // 这里面有空没走的情况
    action.strokeDescriptions.lastOrNull()?.first?.also {
        if (type == END_PATH_2) {
            val sd = it.continueStroke(action.takePath1End(), 0, sTime, true)
            builder.addStroke(sd)
            emptyStroke = false
            replacedCurrSd1 = sd
        } else {
            val sd = it.continueStroke(action.takePath1End(), 0, sTime, false)
            builder.addStroke(sd)
            emptyStroke = false
        }
    }
    action.strokeDescriptions.lastOrNull()?.second?.also {
        if (it.willContinue()) {
            builder.addStroke(it.continueStroke(action.takePath2End(), 0, sTime, false))
            emptyStroke = false
        }
    }
    if (emptyStroke) {
        printLogE { "access releaseLastGestures empty stroke" }
        lastAction = null // release in main thread
        lastRockerDown = null
        lastRockerMove = null
        lastBaseAtk = null
        return@withContext // Cause by java.lang.IllegalStateException Gestures must have at least one stroke
    }

    service.dispatchGesture(builder.build(), null, null)
    if (type == END_ALL) {
        lastAction = null // release in main thread
        lastRockerDown = null
        lastRockerMove = null
        lastBaseAtk = null
    } else if (type == END_PATH_2) {
        lastAction = action.replaceExcludeBtn(invokedSd1 = replacedCurrSd1)
        printLog { "release path1 replace [${lastAction}] sd1 $replacedCurrSd1" }
        lastBaseAtk = null
    }
    delay(sTime)

}

private suspend fun takeService(): ClipboardManagerService? {
    if (Accessibility.itc) {
        delay(1000)
        printLogE { "access intercepted" }
        return null
    }
    val service = ClipboardManagerService.ins
    if (service == null) {
        printLogE { "access service null" }
        return null
    }
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        printLogE { "access system too old" }
        return null
    }
    return service
}

private var checkTs = 0L
private fun check() {
    if (System.currentTimeMillis() - checkTs > 598000) {
        if (Controller.isRunning) {
            if (Keys.isUsingCDKeyInfoActivating != 0) {
                // 卡密已失效[$a]
                Controller.isInvalid.postValue(true)
                DiskLogUtils.saveToDisk()
            }
            checkTs = System.currentTimeMillis()
        }
    }
}
//private val decelerateInterpolator = DecelerateInterpolator(0.85f)
private val decelerateInterpolator = DecelerateInterpolator(3f)
private val accelerateInterpolator = AccelerateInterpolator(1.15f)
private val accelerateDecelerateInterpolator = AccelerateDecelerateInterpolator()
private val overshootInterpolator = OvershootInterpolator(0.85f)


private fun createHumanLikePathList(
    startX: Float, startY: Float,
    endX: Float, endY: Float,
): List<Path> {
//    val interpolator = when(random.nextInt(4)) {
//        0 -> accelerateInterpolator
//        1 -> decelerateInterpolator
//        2 -> accelerateDecelerateInterpolator
//        else -> overshootInterpolator
//    }
    val interpolator = decelerateInterpolator
    // 1. 创建两个中间控制点（带随机偏移）
    val a = (3 .. 47).random() / 100f
    val b = (53 .. 97).random() / 100f
    val (mid1X, mid1Y) = createRandomMidPoint(startX, startY, endX, endY, a)
    val (mid2X, mid2Y) = createRandomMidPoint(startX, startY, endX, endY, b)

    // 2. 在贝塞尔曲线上采样点
    // 4ms一次 63.75点是255毫秒
    // 255 除3.5 = 72 用72波动. 走路走少了感觉
//    val pointCount = (68 .. 76).random()
    val pointCount = 72
    val points = mutableListOf<Pair<Float, Float>>()

    for (i in 0..pointCount) {
        // 计算线性进度 (0.0 - 1.0)
        val linearProgress = i.toFloat() / pointCount
        // 应用插值器获取非线性进度
        val acceleratedProgress = interpolator.getInterpolation(linearProgress)

        // 计算贝塞尔曲线上的点
        val curvePoint = calculateBezierPoint(
            startX, startY,
            mid1X, mid1Y,
            mid2X, mid2Y,
            endX, endY,
            acceleratedProgress
        )

        // 添加速度相关的抖动（中间大，两端小）
        //val maxJitter = 0//(6.pt * sin(acceleratedProgress * PI).toFloat() * 0.8f + 1.5f).toInt()
        val jitterX = 0 //(-maxJitter..maxJitter).random().toFloat()
        val jitterY = 0 //(-maxJitter..maxJitter).random().toFloat()

        // 应用抖动并确保在屏幕边界内
        val finalX = (curvePoint.first + jitterX).coerceIn(clickAreaEdge, phoneScreenWidth - clickAreaEdge)
        val finalY = (curvePoint.second + jitterY).coerceIn(clickAreaEdge, phoneScreenHeight - clickAreaEdge)

        points.add(finalX to finalY)
    }

    // 3. 创建路径段列表
    val pathList = mutableListOf<Path>()
    var lastX = startX
    var lastY = startY

    for (point in points) {
        val (currX, currY) = point

        // 创建新路径段
        val segmentPath = Path()
        segmentPath.moveTo(lastX, lastY)

        // 使用直线连接（因为点足够密集）
        segmentPath.lineTo(currX, currY)

        pathList.add(segmentPath)

        // 更新最后位置
        lastX = currX
        lastY = currY
    }

    // 添加最后一段到终点
    val finalPath = Path()
    finalPath.moveTo(lastX, lastY)
    finalPath.lineTo(endX, endY)
    pathList.add(finalPath)

    return pathList
}

private val takeRandomOffset: Float get() {
//    return when (random.nextInt(3)) {
//        0 -> 0.35f
//        else -> 0.65f
//    }
    return 0.1f
}

// 创建随机中间点（带偏移）
private fun createRandomMidPoint(
    startX: Float, startY: Float,
    endX: Float, endY: Float,
    fraction: Float,
): Pair<Float, Float> {
    // 计算基础位置
    val baseX = startX + (endX - startX) * fraction
    val baseY = startY + (endY - startY) * fraction

    // 计算最大偏移量（基于路径长度的15%）
    val pathLength = hypot(endX - startX, endY - startY)
    val maxOffset = (pathLength * takeRandomOffset).toInt()

    // 添加随机偏移
    val offsetX = (-maxOffset..maxOffset).random()
    val offsetY = (-maxOffset..maxOffset).random()

    // 确保在屏幕边界内
    val finalX = (baseX + offsetX).coerceIn(clickAreaEdge, phoneScreenWidth - clickAreaEdge)
    val finalY = (baseY + offsetY).coerceIn(clickAreaEdge, phoneScreenHeight - clickAreaEdge)

    return finalX to finalY
}

// 计算三次贝塞尔曲线上的点
private fun calculateBezierPoint(
    startX: Float, startY: Float,
    ctrl1X: Float, ctrl1Y: Float,
    ctrl2X: Float, ctrl2Y: Float,
    endX: Float, endY: Float,
    t: Float
): Pair<Float, Float> {
    val u = 1 - t
    val tt = t * t
    val uu = u * u
    val uuu = uu * u
    val ttt = tt * t

    // 三次贝塞尔曲线公式
    val x = uuu * startX +
            3 * uu * t * ctrl1X +
            3 * u * tt * ctrl2X +
            ttt * endX

    val y = uuu * startY +
            3 * uu * t * ctrl1Y +
            3 * u * tt * ctrl2Y +
            ttt * endY

    return x to y
}